package nl.windgazer.arachne.internal.transform;

import java.util.Locale;

import nl.windgazer.arachne.annotations.IncludeCufon;
import nl.windgazer.arachne.services.ArachneRenderSupport;

import org.apache.tapestry5.Asset;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.annotations.SetupRender;
import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Mapper;
import org.apache.tapestry5.func.Worker;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.services.SymbolSource;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.plastic.ComputedValue;
import org.apache.tapestry5.plastic.FieldHandle;
import org.apache.tapestry5.plastic.InstanceContext;
import org.apache.tapestry5.plastic.MethodAdvice;
import org.apache.tapestry5.plastic.MethodInvocation;
import org.apache.tapestry5.plastic.PlasticClass;
import org.apache.tapestry5.plastic.PlasticField;
import org.apache.tapestry5.plastic.PlasticMethod;
import org.apache.tapestry5.services.AssetSource;
import org.apache.tapestry5.services.TransformConstants;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.services.transform.TransformationSupport;

/**
 * Using a simple annotation to define the font you want to Cufon, and what replacements to perform
 * based on that font!
 * Now based on T5.2 PlasticClassWorkers this annotations finally works as intended!!!!
 * 
 * @author mreuring
 *
 */
public class IncludeCufonWorker implements ComponentClassTransformWorker2 {

	public final static String BASEPACKAGE = "classpath:com/shoqolate/cufon/";
	public final static String BASEIDENTIFIER = ".font.js";
	public final static String CUFON = "classpath:com/shoqolate/cufon/cufon-yui.js";
	public final static String CUFONHELPER = "classpath:com/shoqolate/cufon/cufon-helper.js";

    private final SymbolSource symbolSource;

	private ArachneRenderSupport aRenderSupport;

	private JavaScriptSupport jss;

	public IncludeCufonWorker( JavaScriptSupport jss, ArachneRenderSupport aRenderSupport,
                                   SymbolSource symbolSource, AssetSource assetSource)
    {
		this.symbolSource = symbolSource;
        this.assetSource = assetSource;
        this.aRenderSupport = aRenderSupport;
        this.jss = jss;
    }

    private final AssetSource assetSource;

    private final Worker<Asset> importLibrary = new Worker<Asset>()
    {
        public void work(Asset asset)
        {
            jss.importJavaScriptLibrary(asset);
        }
    };

    private final Mapper<String, String> expandSymbols = new Mapper<String, String>()
    {
        public String map(String element)
        {
            return symbolSource.expandSymbols(element);
        }
    };

    /**
     * Here we start, since I mostly understand what Howard was doing, and didn't see any way to re-use his code, copy/past it is.
     * In this particular case I'll be modifying to make use of my own annotation of course... The main thing I need to steal from
     * Howard is how to make classpath strings into Assets...
     * The rest I think I can solve differently :)
     */
    public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model)
    {
        IncludeCufon annotation = componentClass.getAnnotation(IncludeCufon.class);
        processClassAnnotationAtSetupRenderPhase(componentClass, model);
        
        for (String replacement : annotation.replace()) {
        	aRenderSupport.addCufonReplace(replacement);
        }
        for (PlasticMethod m : componentClass.getMethodsWithAnnotation(IncludeCufon.class))
        {
            decorateMethod(componentClass, model, m);
        }
    }

    private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model)
    {
        IncludeCufon annotation = componentClass.getAnnotation(IncludeCufon.class);

        if (annotation != null)
        {
            PlasticMethod setupRender = componentClass.introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION);

            decorateMethod(componentClass, model, setupRender, annotation);

            model.addRenderPhase(SetupRender.class);
        }
    }

    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method)
    {
        IncludeCufon annotation = method.getAnnotation(IncludeCufon.class);

        decorateMethod(componentClass, model, method, annotation);
    }

    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method,
                                IncludeCufon annotation)
    {
        importLibraries(componentClass, model, method, annotation.value());
    }

    private void importLibraries(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
                                 String[] values)
    {
    	//TODO: Transform values into paths, and add default libraries...
    	String[] paths = new String[]{};
        decorateMethodWithOperation(plasticClass, model, method, paths, importLibrary);
    }

    private void decorateMethodWithOperation(PlasticClass componentClass, MutableComponentModel model,
                                             PlasticMethod method, String[] paths, Worker<Asset> operation)
    {
        if (paths.length == 0)
            return;

        String[] expandedPaths = expandPaths(paths);

        PlasticField assetListField = componentClass.introduceField(Asset[].class,
                "importedAssets_" + method.getDescription().methodName);

        initializeAssetsFromPaths(model.getBaseResource(), expandedPaths, assetListField);

        addMethodAssetOperationAdvice(method, assetListField.getHandle(), operation);
    }

    private String[] expandPaths(String[] paths)
    {
        return F.flow(paths).map(expandSymbols).toArray(String.class);
    }

    private void initializeAssetsFromPaths(final Resource baseResource,
                                           final String[] expandedPaths, final PlasticField assetsField)
    {
        assetsField.injectComputed(new ComputedValue<Asset[]>()
        {
            public Asset[] get(InstanceContext context)
            {
                ComponentResources resources = context.get(ComponentResources.class);

                return convertPathsToAssetArray(baseResource, resources.getLocale(), expandedPaths);
            }
        });
    }

    private Asset[] convertPathsToAssetArray(final Resource baseResource, final Locale locale, String[] assetPaths)
    {
        return F.flow(assetPaths).map(new Mapper<String, Asset>()
        {
            public Asset map(String assetPath)
            {
                return assetSource.getAsset(baseResource, assetPath, locale);
            }
        }).toArray(Asset.class);
    }

    private void addMethodAssetOperationAdvice(PlasticMethod method, final FieldHandle access,
                                               final Worker<Asset> operation)
    {
        method.addAdvice(new MethodAdvice()
        {
            public void advise(MethodInvocation invocation)
            {
                Asset[] assets = (Asset[]) access.get(invocation.getInstance());

                F.flow(assets).each(operation);

                invocation.proceed();
            }
        });
    }

}
